from typing import Any, Iterable, Tuple

from typing_inspect import get_args, get_origin
from zkl_pyutils_registries import UNSET

from zkl_pyutils_serialization.basics import get_context_tag_value
from zkl_pyutils_serialization.convert import convert, registry
from zkl_pyutils_serialization.convertors.common import PyObjectFormat
from zkl_pyutils_serialization.tags import DstFormat, DstTypehint, SrcFormat, SrcType
from .format import JsonValueFormat


@registry.register({SrcFormat: PyObjectFormat, DstFormat: JsonValueFormat, SrcType: tuple})
def tuple_serializer(items: tuple) -> list:
    typehint = get_context_tag_value(SrcType)
    item_typehint, items_typehint = parse_tuple_type_args(typehint)
    if item_typehint is not None:
        return [convert(item, src_typehint=item_typehint) for item in items]
    if items_typehint is not None:
        if len(items_typehint) > len(items):
            raise NotImplementedError
        if len(items_typehint) < len(items):
            items_typehint = list(items_typehint) + [UNSET] * (len(items) - len(items_typehint))
        return [convert(item, src_typehint=item_typehint) for item, item_typehint in zip(items, items_typehint)]
    raise AssertionError


@registry.register({SrcFormat: JsonValueFormat, DstFormat: PyObjectFormat, SrcType: Iterable, DstTypehint: tuple})
def tuple_deserializer(items: Iterable) -> tuple:
    if isinstance(items, str):
        raise NotImplementedError  # avoid iterating a string
    items = tuple(items)

    typehint = get_context_tag_value(DstTypehint)
    item_typehint, items_typehint = parse_tuple_type_args(typehint)
    if item_typehint is not None:
        return tuple(convert(item, dst_typehint=item_typehint) for item in items)
    if items_typehint is not None:
        if len(items_typehint) > len(items):
            raise NotImplementedError
        if len(items_typehint) < len(items):
            items_typehint = list(items_typehint) + [UNSET] * (len(items) - len(items_typehint))
        return tuple(convert(item, dst_typehint=item_typehint) for item, item_typehint in zip(items, items_typehint))
    raise AssertionError


def parse_tuple_type_args(typehint: Any):
    origin = get_origin(typehint) or typehint
    origin = tuple if origin is Tuple else origin
    if origin is not tuple:
        raise NotImplementedError

    args = get_args(typehint, evaluate=True)
    if len(args) == 0:
        item_typehint = UNSET
        items_typehint = None
    elif len(args) == 1 and args[0] is Ellipsis:  # tuple[...]
        item_typehint = UNSET
        items_typehint = None
    elif len(args) == 2 and args[1] is Ellipsis:  # tuple[T, ...]
        item_typehint = args[0]
        items_typehint = None
    elif Ellipsis not in args:
        item_typehint = None
        items_typehint = args
    else:
        raise TypeError(f"Invalid type args of {typehint}!")
    return item_typehint, items_typehint
